<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
  <title></title>
  <link href="../Styles/stylesheet.css" rel="stylesheet" type="text/css" />
  
<style type="text/css">
@page { margin-bottom: 5.000000pt; margin-top: 5.000000pt; }
</style>
</head>

<body>
  <h2><span style="border-bottom:1px solid">Chapter_9</span></h2>

  <p>rn label; }</p>

  <p>};</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel8 p = new Parcel8();</p>

  <p>Destination d = p.dest("Tanzania");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>若试图定义一个匿名内部类，并想使用在匿名内部类外部定义的一个对象，则编译器要求外部对象为final属性。这正是我们将dest()的自变量设为final的原因。如果忘记这样做，就会得到一条编译期出错提示。</p>

  <p>只要自己只是想分配一个字段，上述方法就肯定可行。但假如需要采取一些类似于构建器的行动，又应怎样操作呢？通过Java 1.1的实例初始化，我们可以有效地为一个匿名内部类创建一个构建器：</p>

  <p>//: Parcel9.java</p>

  <p>// Using "instance initialization" to perform</p>

  <p>// construction on an anonymous inner class</p>

  <p>package c07.innerscopes;</p>

  <p>public class Parcel9 {</p>

  <p>public Destination</p>

  <p>dest(final String dest, final float price) {</p>

  <p>return new Destination() {</p>

  <p>private int cost;</p>

  <p>// Instance initialization for each object:</p>

  <p>{</p>

  <p>cost = Math.round(price);</p>

  <p>if(cost &gt; 100)</p>

  <p>System.out.println("Over budget!");</p>

  <p>}</p>

  <p>private String label = dest;</p>

  <p>public String readLabel() { return label; }</p>

  <p>};</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel9 p = new Parcel9();</p>

  <p>Destination d = p.dest("Tanzania", 101.395F);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在实例初始化模块中，我们可看到代码不能作为类初始化模块（即if语句）的一部分执行。所以实际上，一个实例初始化模块就是一个匿名内部类的构建器。当然，它的功能是有限的；我们不能对实例初始化模块进行过载处理，所以只能拥有这些构建器的其中一个。</p>

  <p>7.6.3 链接到外部类</p>

  <p>迄今为止，我们见到的内部类好象仅仅是一种名字隐藏以及代码组织方案。尽管这些功能非常有用，但似乎并不特别引人注目。然而，我们还忽略了另一个重要的事实。创建自己的内部类时，那个类的对象同时拥有指向封装对象（这些对象封装或生成了内部类）的一个链接。所以它们能访问那个封装对象的成员――毋需取得任何资格。除此以外，内部类拥有对封装类所有元素的访问权限（注释②）。下面这个例子阐示了这个问题：</p>

  <p>//: Sequence.java</p>

  <p>// Holds a sequence of Objects</p>

  <p>interface Selector {</p>

  <p>boolean end();</p>

  <p>Object current();</p>

  <p>void next();</p>

  <p>}</p>

  <p>public class Sequence {</p>

  <p>private Object[] o;</p>

  <p>private int next = 0;</p>

  <p>public Sequence(int size) {</p>

  <p>o = new Object[size];</p>

  <p>}</p>

  <p>public void add(Object x) {</p>

  <p>if(next &lt; o.length) {</p>

  <p>o[next] = x;</p>

  <p>next++;</p>

  <p>}</p>

  <p>}</p>

  <p>private class SSelector implements Selector {</p>

  <p>int i = 0;</p>

  <p>public boolean end() {</p>

  <p>return i == o.length;</p>

  <p>}</p>

  <p>public Object current() {</p>

  <p>return o[i];</p>

  <p>}</p>

  <p>public void next() {</p>

  <p>if(i &lt; o.length) i++;</p>

  <p>}</p>

  <p>}</p>

  <p>public Selector getSelector() {</p>

  <p>return new SSelector();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Sequence s = new Sequence(10);</p>

  <p>for(int i = 0; i &lt; 10; i++)</p>

  <p>s.add(Integer.toString(i));</p>

  <p>Selector sl = s.getSelector();</p>

  <p>while(!sl.end()) {</p>

  <p>System.out.println((String)sl.current());</p>

  <p>sl.next();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>②：这与C++“嵌套类”的设计颇有不同，后者只是一种单纯的名字隐藏机制。在C++中，没有指向一个封装对象的链接，也不存在默认的访问权限。</p>

  <p>其中，Sequence只是一个大小固定的对象数组，有一个类将其封装在内部。我们调用add()，以便将一个新对象添加到Sequence末尾（如果还有地方的话）。为了取得Sequence中的每一个对象，要使用一个名为Selector的接口，它使我们能够知道自己是否位于最末尾（end()），能观看当前对象（current() Object），以及能够移至Sequence内的下一个对象（next() Object）。由于Selector是一个接口，所以其他许多类都能用它们自己的方式实现接口，而且许多方法都能将接口作为一个自变量使用，从而创建一般的代码。</p>

  <p>在这里，SSelector是一个私有类，它提供了Selector功能。在main()中，大家可看到Sequence的创建过程，在它后面是一系列字串对象的添加。随后，通过对getSelector()的一个调用生成一个Selector。并用它在Sequence中移动，同时选择每一个项目。</p>

  <p>从表面看，SSelector似乎只是另一个内部类。但不要被表面现象迷惑。请注意观察end()，current()以及next()，它们每个方法都引用了o。o是个不属于SSelector一部分的句柄，而是位于封装类里的一个private字段。然而，内部类可以从封装类访问方法与字段，就象已经拥有了它们一样。这一特征对我们来说是非常方便的，就象在上面的例子中看到的那样。</p>

  <p>因此，我们现在知道一个内部类可以访问封装类的成员。这是如何实现的呢？内部类必须拥有对封装类的特定对象的一个引用，而封装类的作用就是创建这个内部类。随后，当我们引用封装类的一个成员时，就利用那个（隐藏）的引用来选择那个成员。幸运的是，编译器会帮助我们照管所有这些细节。但我们现在也可以理解内部类的一个对象只能与封装类的一个对象联合创建。在这个创建过程中，要求对封装类对象的句柄进行初始化。若不能访问那个句柄，编译器就会报错。进行所有这些操作的时候，大多数时候都不要求程序员的任何介入。</p>

  <p>7.6.4 static内部类</p>

  <p>为正确理解static在应用于内部类时的含义，必须记住内部类的对象默认持有创建它的那个封装类的一个对象的句柄。然而，假如我们说一个内部类是static的，这种说法却是不成立的。static内部类意味着：</p>

  <p>(1) 为创建一个static内部类的对象，我们不需要一个外部类对象。</p>

  <p>(2) 不能从static内部类的一个对象中访问一个外部类对象。</p>

  <p>但在存在一些限制：由于static成员只能位于一个类的外部级别，所以内部类不可拥有static数据或static内部类。</p>

  <p>倘若为了创建内部类的对象而不需要创建外部类的一个对象，那么可将所有东西都设为static。为了能正常工作，同时也必须将内部类设为static。如下所示：</p>

  <p>//: Parcel10.java</p>

  <p>// Static inner classes</p>

  <p>package c07.parcel10;</p>

  <p>abstract class Contents {</p>

  <p>abstract public int value();</p>

  <p>}</p>

  <p>interface Destination {</p>

  <p>String readLabel();</p>

  <p>}</p>

  <p>public class Parcel10 {</p>

  <p>private static class PContents</p>

  <p>extends Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>protected static class PDestination</p>

  <p>implements Destination {</p>

  <p>private String label;</p>

  <p>private PDestination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>public String readLabel() { return label; }</p>

  <p>}</p>

  <p>public static Destination dest(String s) {</p>

  <p>return new PDestination(s);</p>

  <p>}</p>

  <p>public static Contents cont() {</p>

  <p>return new PContents();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Contents c = cont();</p>

  <p>Destination d = dest("Tanzania");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在main()中，我们不需要Parcel10的对象；相反，我们用常规的语法来选择一个static成员，以便调用将句柄返回Contents和Destination的方法。</p>

  <p>通常，我们不在一个接口里设置任何代码，但static内部类可以成为接口的一部分。由于类是“静态”的，所以它不会违反接口的规则――static内部类只位于接口的命名空间内部：</p>

  <p>//: IInterface.java</p>

  <p>// Static inner classes inside interfaces</p>

  <p>interface IInterface {</p>

  <p>static class Inner {</p>

  <p>int i, j, k;</p>

  <p>public Inner() {}</p>

  <p>void f() {}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在本书早些时候，我建议大家在每个类里都设置一个main()，将其作为那个类的测试床使用。这样做的一个缺点就是额外代码的数量太多。若不愿如此，可考虑用一个static内部类容纳自己的测试代码。如下所示：</p>

  <p>//: TestBed.java</p>

  <p>// Putting test code in a static inner class</p>

  <p>class TestBed {</p>

  <p>TestBed() {}</p>

  <p>void f() { System.out.println("f()"); }</p>

  <p>public static class Tester {</p>

  <p>public static void main(String[] args) {</p>

  <p>TestBed t = new TestBed();</p>

  <p>t.f();</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这样便生成一个独立的、名为TestBed$Tester的类（为运行程序，请使用“java TestBed$Tester”命令）。可将这个类用于测试，但不需在自己的最终发行版本中包含它。</p>

  <p>7.6.5 引用外部类对象</p>

  <p>若想生成外部类对象的句柄，就要用一个点号以及一个this来命名外部类。举个例子来说，在Sequence.SSelector类中，它的所有方法都能产生外部类Sequence的存储句柄，方法是采用Sequence.this的形式。结果获得的句柄会自动具备正确的类型（这会在编译期间检查并核实，所以不会出现运行期的开销）。</p>

  <p>有些时候，我们想告诉其他某些对象创建它某个内部类的一个对象。为达到这个目的，必须在new表达式中提供指向其他外部类对象的一个句柄，就象下面这样：</p>

  <p>//: Parcel11.java</p>

  <p>// Creating inner classes</p>

  <p>package c07.parcel11;</p>

  <p>public class Parcel11 {</p>

  <p>class Contents {</p>

  <p>private int i = 11;</p>

  <p>public int value() { return i; }</p>

  <p>}</p>

  <p>class Destination {</p>

  <p>private String label;</p>

  <p>Destination(String whereTo) {</p>

  <p>label = whereTo;</p>

  <p>}</p>

  <p>String readLabel() { return label; }</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>Parcel11 p = new Parcel11();</p>

  <p>// Must use instance of outer class</p>

  <p>// to create an instances of the inner class:</p>

  <p>Parcel11.Contents c = p.new Contents();</p>

  <p>Parcel11.Destination d =</p>

  <p>p.new Destination("Tanzania");</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>为直接创建内部类的一个对象，不能象大家或许猜想的那样――采用相同的形式，并引用外部类名Parcel11。此时，必须利用外部类的一个对象生成内部类的一个对象：</p>

  <p>Parcel11.Contents c = p.new Contents();</p>

  <p>因此，除非已拥有外部类的一个对象，否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建它的外部类的对象“默默”地连接到一起。然而，如果生成一个static内部类，就不需要指向外部类对象的一个句柄。</p>

  <p>7.6.6 从内部类继承</p>

  <p>由于内部类构建器必须同封装类对象的一个句柄联系到一起，所以从一个内部类继承的时候，情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化，而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法，明确建立这种关联：</p>

  <p>//: InheritInner.java</p>

  <p>// Inheriting an inner class</p>

  <p>class WithInner {</p>

  <p>class Inner {}</p>

  <p>}</p>

  <p>public class InheritInner</p>

  <p>extends WithInner.Inner {</p>

  <p>//! InheritInner() {} // Won't compile</p>

  <p>InheritInner(WithInner wi) {</p>

  <p>wi.super();</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>WithInner wi = new WithInner();</p>

  <p>InheritInner ii = new InheritInner(wi);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>从中可以看到，InheritInner只对内部类进行了扩展，没有扩展外部类。但在需要创建一个构建器的时候，默认对象已经没有意义，我们不能只是传递封装对象的一个句柄。此外，必须在构建器中采用下述语法：</p>

  <p>enclosingClassHandle.super();</p>

  <p>它提供了必要的句柄，以便程序正确编译。</p>

  <p>7.6.7 内部类可以覆盖吗？</p>

  <p>若创建一个内部类，然后从封装类继承，并重新定义内部类，那么会出现什么情况呢？也就是说，我们有可能覆盖一个内部类吗？这看起来似乎是一个非常有用的概念，但“覆盖”一个内部类――好象它是外部类的另一个方法――这一概念实际不能做任何事情：</p>

  <p>//: BigEgg.java</p>

  <p>// An inner class cannot be overriden</p>

  <p>// like a method</p>

  <p>class Egg {</p>

  <p>protected class Yolk {</p>

  <p>public Yolk() {</p>

  <p>System.out.println("Egg.Yolk()");</p>

  <p>}</p>

  <p>}</p>

  <p>private Yolk y;</p>

  <p>public Egg() {</p>

  <p>System.out.println("New Egg()");</p>

  <p>y = new Yolk();</p>

  <p>}</p>

  <p>}</p>

  <p>public class BigEgg extends Egg {</p>

  <p>public class Yolk {</p>

  <p>public Yolk() {</p>

  <p>System.out.println("BigEgg.Yolk()");</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>new BigEgg();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>默认构建器是由编译器自动合成的，而且会调用基础类的默认构建器。大家或许会认为由于准备创建一个BigEgg，所以会使用Yolk的“被覆盖”版本。但实际情况并非如此。输出如下：</p>

  <p>New Egg()</p>

  <p>Egg.Yolk()</p>

  <p>这个例子简单地揭示出当我们从外部类继承的时候，没有任何额外的内部类继续下去。然而，仍然有可能“明确”地从内部类继承：</p>

  <p>//: BigEgg2.java</p>

  <p>// Proper inheritance of an inner class</p>

  <p>class Egg2 {</p>

  <p>protected class Yolk {</p>

  <p>public Yolk() {</p>

  <p>System.out.println("Egg2.Yolk()");</p>

  <p>}</p>

  <p>public void f() {</p>

  <p>System.out.println("Egg2.Yolk.f()");</p>

  <p>}</p>

  <p>}</p>

  <p>private Yolk y = new Yolk();</p>

  <p>public Egg2() {</p>

  <p>System.out.println("New Egg2()");</p>

  <p>}</p>

  <p>public void insertYolk(Yolk yy) { y = yy; }</p>

  <p>public void g() { y.f(); }</p>

  <p>}</p>

  <p>public class BigEgg2 extends Egg2 {</p>

  <p>public class Yolk extends Egg2.Yolk {</p>

  <p>public Yolk() {</p>

  <p>System.out.println("BigEgg2.Yolk()");</p>

  <p>}</p>

  <p>public void f() {</p>

  <p>System.out.println("BigEgg2.Yolk.f()");</p>

  <p>}</p>

  <p>}</p>

  <p>public BigEgg2() { insertYolk(new Yolk()); }</p>

  <p>public static void main(String[] args) {</p>

  <p>Egg2 e2 = new BigEgg2();</p>

  <p>e2.g();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>现在，BigEgg2.Yolk明确地扩展了Egg2.Yolk，而且覆盖了它的方法。方法insertYolk()允许BigEgg2将它自己的某个Yolk对象上溯造型至Egg2的y句柄。所以当g()调用y.f()的时候，就会使用f()被覆盖版本。输出结果如下：</p>

  <p>Egg2.Yolk()</p>

  <p>New Egg2()</p>

  <p>Egg2.Yolk()</p>

  <p>BigEgg2.Yolk()</p>

  <p>BigEgg2.Yolk.f()</p>

  <p>对Egg2.Yolk()的第二个调用是BigEgg2.Yolk构建器的基础类构建器调用。调用</p>

  <p>g()的时候，可发现使用的是f()的被覆盖版本。</p>

  <p>7.6.8 内部类标识符</p>

  <p>由于每个类都会生成一个.class文件，用于容纳与如何创建这个类型的对象有关的所有信息（这种信息产生了一个名为Class对象的元类），所以大家或许会猜到内部类也必须生成相应的.class文件，用来容纳与它们的Class对象有关的信息。这些文件或类的名字遵守一种严格的形式：先是封装类的名字，再跟随一个$，再跟随内部类的名字。例如，由InheritInner.java创建的.class文件包括：</p>

  <p>InheritInner.class</p>

  <p>WithInner$Inner.class</p>

  <p>WithInner.class</p>

  <p>如果内部类是匿名的，那么编译器会简单地生成数字，把它们作为内部类标识符使用。若内部类嵌套于其他内部类中，则它们的名字简单地追加在一个$以及外部类标识符的后面。</p>

  <p>这种生成内部名称的方法除了非常简单和直观以外，也非常“健壮”，可适应大多数场合的要求（注释③）。由于它是Java的标准命名机制，所以产生的文件会自动具备“与平台无关”的能力（注意Java编译器会根据情况改变内部类，使其在不同的平台中能正常工作）。</p>

  <p>③：但在另一方面，由于“$”也是Unix外壳的一个元字符，所以有时会在列出.class文件时遇到麻烦。对一家以Unix为基础的公司――Sun――来说，采取这种方案显得有些奇怪。我的猜测是他们根本没有仔细考虑这方面的问题，而是认为我们会将全部注意力自然地放在源码文件上。</p>

  <p>7.6.9 为什么要用内部类：控制框架</p>

  <p>到目前为止，大家已接触了对内部类的运作进行描述的大量语法与概念。但这些并不能真正说明内部类存在的原因。为什么Sun要如此麻烦地在Java 1.1里添加这样的一种基本语言特性呢？答案就在于我们在这里要学习的“控制框架”。</p>

  <p>一个“应用程序框架”是指一个或一系列类，它们专门设计用来解决特定类型的问题。为应用应用程序框架，我们可从一个或多个类继承，并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些应用程序框架提供的常规方案，以便解决自己的实际问题。“控制框架”属于应用程序框架的一种特殊类型，受到对事件响应的需要的支配；主要用来响应事件的一个系统叫作“由事件驱动的系统”。在应用程序设计语言中，最重要的问题之一便是“图形用户界面”（GUI），它几乎完全是由事件驱动的。正如大家会在第13章学习的那样，Java 1.1 AWT属于一种控制框架，它通过内部类完美地解决了GUI的问题。</p>

  <p>为理解内部类如何简化控制框架的创建与使用，可认为一个控制框架的工作就是在事件“就绪”以后执行它们。尽管“就绪”的意思很多，但在目前这种情况下，我们却是以计算机时钟为基础。随后，请认识到针对控制框架需要控制的东西，框架内并未包含任何特定的信息。首先，它是一个特殊的接口，描述了所有控制事件。它可以是一个抽象类，而非一个实际的接口。由于默认行为是根据时间控制的，所以部分实施细节可能包括：</p>

  <p>//: Event.java</p>

  <p>// The common methods for any control event</p>

  <p>package c07.controller;</p>

  <p>abstract public class Event {</p>

  <p>private long evtTime;</p>

  <p>public Event(long eventTime) {</p>

  <p>evtTime = eventTime;</p>

  <p>}</p>

  <p>public boolean ready() {</p>

  <p>return System.currentTimeMillis() &gt;= evtTime;</p>

  <p>}</p>

  <p>abstract public void action();</p>

  <p>abstract public String description();</p>

  <p>} ///:~</p>

  <p>希望Event（事件）运行的时候，构建器即简单地捕获时间。同时ready()告诉我们何时该运行它。当然，ready()也可以在一个衍生类中被覆盖，将事件建立在除时间以外的其他东西上。</p>

  <p>action()是事件就绪后需要调用的方法，而description()提供了与事件有关的文字信息。</p>

  <p>下面这个文件包含了实际的控制框架，用于管理和触发事件。第一个类实际只是一个“助手”类，它的职责是容纳Event对象。可用任何适当的集合替换它。而且通过第8章的学习，大家会知道另一些集合可简化我们的工作，不需要我们编写这些额外的代码：</p>

  <p>//: Controller.java</p>

  <p>// Along with Event, the generic</p>

  <p>// framework for all control systems:</p>

  <p>package c07.controller;</p>

  <p>// This is just a way to hold Event objects.</p>

  <p>class EventSet {</p>

  <p>private Event[] events = new Event[100];</p>

  <p>private int index = 0;</p>

  <p>private int next = 0;</p>

  <p>public void add(Event e) {</p>

  <p>if(index &gt;= events.length)</p>

  <p>return; // (In real life, throw exception)</p>

  <p>events[index++] = e;</p>

  <p>}</p>

  <p>public Event getNext() {</p>

  <p>boolean looped = false;</p>

  <p>int start = next;</p>

  <p>do {</p>

  <p>next = (next + 1) % events.length;</p>

  <p>// See if it has looped to the beginning:</p>

  <p>if(start == next) looped = true;</p>

  <p>// If it loops past start, the list</p>

  <p>// is empty:</p>

  <p>if((next == (start + 1) % events.length)</p>

  <p>&amp;&amp; looped)</p>

  <p>return null;</p>

  <p>} while(events[next] == null);</p>

  <p>return events[next];</p>

  <p>}</p>

  <p>public void removeCurrent() {</p>

  <p>events[next] = null;</p>

  <p>}</p>

  <p>}</p>

  <p>public class Controller {</p>

  <p>private EventSet es = new EventSet();</p>

  <p>public void addEvent(Event c) { es.add(c); }</p>

  <p>public void run() {</p>

  <p>Event e;</p>

  <p>while((e = es.getNext()) != null) {</p>

  <p>if(e.ready()) {</p>

  <p>e.action();</p>

  <p>System.out.println(e.description());</p>

  <p>es.removeCurrent();</p>

  <p>}</p>

  <p>}</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>EventSet可容纳100个事件（若在这里使用来自第8章的一个“真实”集合，就不必担心它的最大尺寸，因为它会根据情况自动改变大小）。index（索引）在这里用于跟踪下一个可用的空间，而next（下一个）帮助我们寻找列表中的下一个事件，了解自己是否已经循环到头。在对getNext()的调用中，这一点是至关重要的，因为一旦运行，Event对象就会从列表中删去（使用removeCurrent()）。所以getNext()会在列表中向前移动时遇到“空洞”。</p>

  <p>注意removeCurrent()并不只是指示一些标志，指出对象不再使用。相反，它将句柄设为null。这一点是非常重要的，因为假如垃圾收集器发现一个句柄仍在使用，就不会清除对象。若认为自己的句柄可能象现在这样被挂起，那么最好将其设为null，使垃圾收集器能够正常地清除它们。</p>

  <p>Controller是进行实际工作的地方。它用一个EventSet容纳自己的Event对象，而且addEvent()允许我们向这个列表加入新事件。但最重要的方法是run()。该方法会在EventSet中遍历，搜索一个准备运行的Event对象――ready()。对于它发现ready()的每一个对象，都会调用action()方法，打印出description()，然后将事件从列表中删去。</p>

  <p>注意在迄今为止的所有设计中，我们仍然不能准确地知道一个“事件”要做什么。这正是整个设计的关键；它怎样“将发生变化的东西同没有变化的东西区分开”？或者用我的话来讲，“改变的意图”造成了各类Event对象的不同行动。我们通过创建不同的Event子类，从而表达出不同的行动。</p>

  <p>这里正是内部类大显身手的地方。它们允许我们做两件事情：</p>

  <p>(1) 在单独一个类里表达一个控制框架应用的全部实施细节，从而完整地封装与那个实施有关的所有东西。内部类用于表达多种不同类型的action()，它们用于解决实际的问题。除此以外，后续的例子使用了private内部类，所以实施细节会完全隐藏起来，可以安全地修改。</p>

  <p>(2) 内部类使我们具体的实施变得更加巧妙，因为能方便地访问外部类的任何成员。若不具备这种能力，代码看起来就可能没那么使人舒服，最后不得不寻找其他方法解决。</p>

  <p>现在要请大家思考控制框架的一种具体实施方式，它设计用来控制温室（Greenhouse）功能（注释④）。每个行动都是完全不同的：控制灯光、供水以及温度自动调节的开与关，控制响铃，以及重新启动系统。但控制框架的设计宗旨是将不同的代码方便地隔离开。对每种类型的行动，都要继承一个新的Event内部类，并在action()内编写相应的控制代码。</p>

  <p>④：由于某些特殊原因，这对我来说是一个经常需要解决的、非常有趣的问题；原来的例子在《C++ Inside &amp; Out》一书里也出现过，但Java提供了一种更令人舒适的解决方案。</p>

  <p>作为应用程序框架的一种典型行为，GreenhouseControls类是从Controller继承的：</p>

  <p>//: GreenhouseControls.java</p>

  <p>// This produces a specific application of the</p>

  <p>// control system, all in a single class. Inner</p>

  <p>// classes allow you to encapsulate different</p>

  <p>// functionality for each type of event.</p>

  <p>package c07.controller;</p>

  <p>public class GreenhouseControls</p>

  <p>extends Controller {</p>

  <p>private boolean light = false;</p>

  <p>private boolean water = false;</p>

  <p>private String thermostat = "Day";</p>

  <p>private class LightOn extends Event {</p>

  <p>public LightOn(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here to</p>

  <p>// physically turn on the light.</p>

  <p>light = true;</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Light is on";</p>

  <p>}</p>

  <p>}</p>

  <p>private class LightOff extends Event {</p>

  <p>public LightOff(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here to</p>

  <p>// physically turn off the light.</p>

  <p>light = false;</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Light is off";</p>

  <p>}</p>

  <p>}</p>

  <p>private class WaterOn extends Event {</p>

  <p>public WaterOn(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here</p>

  <p>water = true;</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Greenhouse water is on";</p>

  <p>}</p>

  <p>}</p>

  <p>private class WaterOff extends Event {</p>

  <p>public WaterOff(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here</p>

  <p>water = false;</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Greenhouse water is off";</p>

  <p>}</p>

  <p>}</p>

  <p>private class ThermostatNight extends Event {</p>

  <p>public ThermostatNight(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here</p>

  <p>thermostat = "Night";</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Thermostat on night setting";</p>

  <p>}</p>

  <p>}</p>

  <p>private class ThermostatDay extends Event {</p>

  <p>public ThermostatDay(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Put hardware control code here</p>

  <p>thermostat = "Day";</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Thermostat on day setting";</p>

  <p>}</p>

  <p>}</p>

  <p>// An example of an action() that inserts a</p>

  <p>// new one of itself into the event list:</p>

  <p>private int rings;</p>

  <p>private class Bell extends Event {</p>

  <p>public Bell(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>// Ring bell every 2 seconds, rings times:</p>

  <p>System.out.println("Bing!");</p>

  <p>if(--rings &gt; 0)</p>

  <p>addEvent(new Bell(</p>

  <p>System.currentTimeMillis() + 2000));</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Ring bell";</p>

  <p>}</p>

  <p>}</p>

  <p>private class Restart extends Event {</p>

  <p>public Restart(long eventTime) {</p>

  <p>super(eventTime);</p>

  <p>}</p>

  <p>public void action() {</p>

  <p>long tm = System.currentTimeMillis();</p>

  <p>// Instead of hard-wiring, you could parse</p>

  <p>// configuration information from a text</p>

  <p>// file here:</p>

  <p>rings = 5;</p>

  <p>addEvent(new ThermostatNight(tm));</p>

  <p>addEvent(new LightOn(tm + 1000));</p>

  <p>addEvent(new LightOff(tm + 2000));</p>

  <p>addEvent(new WaterOn(tm + 3000));</p>

  <p>addEvent(new WaterOff(tm + 8000));</p>

  <p>addEvent(new Bell(tm + 9000));</p>

  <p>addEvent(new ThermostatDay(tm + 10000));</p>

  <p>// Can even add a Restart object!</p>

  <p>addEvent(new Restart(tm + 20000));</p>

  <p>}</p>

  <p>public String description() {</p>

  <p>return "Restarting system";</p>

  <p>}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>GreenhouseControls gc =</p>

  <p>new GreenhouseControls();</p>

  <p>long tm = System.currentTimeMillis();</p>

  <p>gc.addEvent(gc.new Restart(tm));</p>

  <p>gc.run();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>注意light（灯光）、water（供水）、thermostat（调温）以及rings都隶属于外部类GreenhouseControls，所以内部类可以毫无阻碍地访问那些字段。此外，大多数action()方法也涉及到某些形式的硬件控制，这通常都要求发出对非Java代码的调用。</p>

  <p>大多数Event类看起来都是相似的，但Bell（铃）和Restart（重启）属于特殊情况。Bell会发出响声，若尚未响铃足够的次数，它会在事件列表里添加一个新的Bell对象，所以以后会再度响铃。请注意内部类看起来为什么总是类似于多重继承：Bell拥有Event的所有方法，而且也拥有外部类GreenhouseControls的所有方法。</p>

  <p>Restart负责对系统进行初始化，所以会添加所有必要的事件。当然，一种更灵活的做法是避免进行“硬编码”，而是从一个文件里读入它们（第10章的一个练习会要求大家修改这个例子，从而达到这个目标）。由于Restart()仅仅是另一个Event对象，所以也可以在Restart.action()里添加一个Restart对象，使系统能够定期重启。在main()中，我们需要做的全部事情就是创建一个GreenhouseControls对象，并添加一个Restart对象，令其工作起来。</p>

  <p>这个例子应该使大家对内部类的价值有一个更加深刻的认识，特别是在一个控制框架里使用它们的时候。此外，在第13章的后半部分，大家还会看到如何巧妙地利用内部类描述一个图形用户界面的行为。完成那里的学习后，对内部类的认识将上升到一个前所未有的新高度。</p>

  <p>7.7 构建器和多形性</p>

  <p>同往常一样，构建器与其他种类的方法是有区别的。在涉及到多形性的问题后，这种方法依然成立。尽管构建器并不具有多形性（即便可以使用一种“虚拟构建器”――将在第11章介绍），但仍然非常有必要理解构建器如何在复杂的分级结构中以及随同多形性使用。这一理解将有助于大家避免陷入一些令人不快的纠纷。</p>

  <p>7.7.1 构建器的调用顺序</p>

  <p>构建器调用的顺序已在第4章进行了简要说明，但那是在继承和多形性问题引入之前说的话。</p>

  <p>用于基础类的构建器肯定在一个衍生类的构建器中调用，而且逐渐向上链接，使每个基础类使用的构建器都能得到调用。之所以要这样做，是由于构建器负有一项特殊任务：检查对象是否得到了正确的构建。一个衍生类只能访问它自己的成员，不能访问基础类的成员（这些成员通常都具有private属性）。只有基础类的构建器在初始化自己的元素时才知道正确的方法以及拥有适当的权限。所以，必须令所有构建器都得到调用，否则整个对象的构建就可能不正确。那正是编译器为什么要强迫对衍生类的每个部分进行构建器调用的原因。在衍生类的构建器主体中，若我们没有明确指定对一个基础类构建器的调用，它就会“默默”地调用默认构建器。如果不存在默认构建器，编译器就会报告一个错误（若某个类没有构建器，编译器会自动组织一个默认构建器）。</p>

  <p>下面让我们看看一个例子，它展示了按构建顺序进行合成、继承以及多形性的效果：</p>

  <p>//: Sandwich.java</p>

  <p>// Order of constructor calls</p>

  <p>class Meal {</p>

  <p>Meal() { System.out.println("Meal()"); }</p>

  <p>}</p>

  <p>class Bread {</p>

  <p>Bread() { System.out.println("Bread()"); }</p>

  <p>}</p>

  <p>class Cheese {</p>

  <p>Cheese() { System.out.println("Cheese()"); }</p>

  <p>}</p>

  <p>class Lettuce {</p>

  <p>Lettuce() { System.out.println("Lettuce()"); }</p>

  <p>}</p>

  <p>class Lunch extends Meal {</p>

  <p>Lunch() { System.out.println("Lunch()");}</p>

  <p>}</p>

  <p>class PortableLunch extends Lunch {</p>

  <p>PortableLunch() {</p>

  <p>System.out.println("PortableLunch()");</p>

  <p>}</p>

  <p>}</p>

  <p>class Sandwich extends PortableLunch {</p>

  <p>Bread b = new Bread();</p>

  <p>Cheese c = new Cheese();</p>

  <p>Lettuce l = new Lettuce();</p>

  <p>Sandwich() {</p>

  <p>System.out.println("Sandwich()");</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>new Sandwich();</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>这个例子在其他类的外部创建了一个复杂的类，而且每个类都有一个构建器对自己进行了宣布。其中最重要的类是Sandwich，它反映出了三个级别的继承（若将从Object的默认继承算在内，就是四级）以及三个成员对象。在main()里创建了一个Sandwich对象后，输出结果如下：</p>

  <p>Meal()</p>

  <p>Lunch()</p>

  <p>PortableLunch()</p>

  <p>Bread()</p>

  <p>Cheese()</p>

  <p>Lettuce()</p>

  <p>Sandwich()</p>

  <p>这意味着对于一个复杂的对象，构建器的调用遵照下面的顺序：</p>

  <p>(1) 调用基础类构建器。这个步骤会不断重复下去，首先得到构建的是分级结构的根部，然后是下一个衍生类，等等。直到抵达最深一层的衍生类。</p>

  <p>(2) 按声明顺序调用成员初始化模块。</p>

  <p>(3) 调用衍生构建器的主体。</p>

  <p>构建器调用的顺序是非常重要的。进行继承时，我们知道关于基础类的一切，并且能访问基础类的任何public和protected成员。这意味着当我们在衍生类的时候，必须能假定基础类的所有成员都是有效的。采用一种标准方法，构建行动已经进行，所以对象所有部分的成员均已得到构建。但在构建器内部，必须保证使用的所有成员都已构建。为达到这个要求，唯一的办法就是首先调用基础类构建器。然后在进入衍生类构建器以后，我们在基础类能够访问的所有成员都已得到初始化。此外，所有成员对象（亦即通过合成方法置于类内的对象）在类内进行定义的时候（比如上例中的b，c和l），由于我们应尽可能地对它们进行初始化，所以也应保证构建器内部的所有成员均为有效。若坚持按这一规则行事，会有助于我们确定所有基础类成员以及当前对象的成员对象均已获得正确的初始化。但不幸的是，这种做法并不适用于所有情况，这将在下一节具体说明。</p>

  <p>7.7.2 继承和finalize()</p>

  <p>通过“合成”方法创建新类时，永远不必担心对那个类的成员对象的收尾工作。每个成员都是一个独立的对象，所以会得到正常的垃圾收集以及收尾处理――无论它是不是不自己某个类一个成员。但在进行初始化的时候，必须覆盖衍生类中的finalize()方法――如果已经设计了某个特殊的清除进程，要求它必须作为垃圾收集的一部分进行。覆盖衍生类的finalize()时，务必记住调用finalize()的基础类版本。否则，基础类的初始化根本不会发生。下面这个例子便是明证：</p>

  <p>//: Frog.java</p>

  <p>// Testing finalize with inheritance</p>

  <p>class DoBaseFinalization {</p>

  <p>public static boolean flag = false;</p>

  <p>}</p>

  <p>class Characteristic {</p>

  <p>String s;</p>

  <p>Characteristic(String c) {</p>

  <p>s = c;</p>

  <p>System.out.println(</p>

  <p>"Creating Characteristic " + s);</p>

  <p>}</p>

  <p>protected void finalize() {</p>

  <p>System.out.println(</p>

  <p>"finalizing Characteristic " + s);</p>

  <p>}</p>

  <p>}</p>

  <p>class LivingCreature {</p>

  <p>Characteristic p =</p>

  <p>new Characteristic("is alive");</p>

  <p>LivingCreature() {</p>

  <p>System.out.println("LivingCreature()");</p>

  <p>}</p>

  <p>protected void finalize() {</p>

  <p>System.out.println(</p>

  <p>"LivingCreature finalize");</p>

  <p>// Call base-class version LAST!</p>

  <p>if(DoBaseFinalization.flag)</p>

  <p>try {</p>

  <p>super.finalize();</p>

  <p>} catch(Throwable t) {}</p>

  <p>}</p>

  <p>}</p>

  <p>class Animal extends LivingCreature {</p>

  <p>Characteristic p =</p>

  <p>new Characteristic("has heart");</p>

  <p>Animal() {</p>

  <p>System.out.println("Animal()");</p>

  <p>}</p>

  <p>protected void finalize() {</p>

  <p>System.out.println("Animal finalize");</p>

  <p>if(DoBaseFinalization.flag)</p>

  <p>try {</p>

  <p>super.finalize();</p>

  <p>} catch(Throwable t) {}</p>

  <p>}</p>

  <p>}</p>

  <p>class Amphibian extends Animal {</p>

  <p>Characteristic p =</p>

  <p>new Characteristic("can live in water");</p>

  <p>Amphibian() {</p>

  <p>System.out.println("Amphibian()");</p>

  <p>}</p>

  <p>protected void finalize() {</p>

  <p>System.out.println("Amphibian finalize");</p>

  <p>if(DoBaseFinalization.flag)</p>

  <p>try {</p>

  <p>super.finalize();</p>

  <p>} catch(Throwable t) {}</p>

  <p>}</p>

  <p>}</p>

  <p>public class Frog extends Amphibian {</p>

  <p>Frog() {</p>

  <p>System.out.println("Frog()");</p>

  <p>}</p>

  <p>protected void finalize() {</p>

  <p>System.out.println("Frog finalize");</p>

  <p>if(DoBaseFinalization.flag)</p>

  <p>try {</p>

  <p>super.finalize();</p>

  <p>} catch(Throwable t) {}</p>

  <p>}</p>

  <p>public static void main(String[] args) {</p>

  <p>if(args.length != 0 &amp;&amp;</p>

  <p>args[0].equals("finalize"))</p>

  <p>DoBaseFinalization.flag = true;</p>

  <p>else</p>

  <p>System.out.println("not finalizing bases");</p>

  <p>new Frog(); // Instantly becomes garbage</p>

  <p>System.out.println("bye!");</p>

  <p>// Must do this to guarantee that all</p>

  <p>// finalizers will be called:</p>

  <p>System.runFinalizersOnExit(true);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>DoBasefinalization类只是简单地容纳了一个标志，向分级结构中的每个类指出是否应调用super.finalize()。这个标志的设置建立在命令行参数的基础上，所以能够在进行和不进行基础类收尾工作的前提下查看行为。</p>

  <p>分级结构中的每个类也包含了Characteristic类的一个成员对象。大家可以看到，无论是否调用了基础类收尾模块，Characteristic成员对象都肯定会得到收尾（清除）处理。</p>

  <p>每个被覆盖的finalize()至少要拥有对protected成员的访问权力，因为Object类中的finalize()方法具有protected属性，而编译器不允许我们在继承过程中消除访问权限（“友好的”比“受到保护的”具有更小的访问权限）。</p>

  <p>在Frog.main()中，DoBaseFinalization标志会得到配置，而且会创建单独一个Frog对象。请记住垃圾收集（特别是收尾工作）可能不会针对任何特定的对象发生，所以为了强制采取这一行动，System.runFinalizersOnExit(true)添加了额外的开销，以保证收尾工作的正常进行。若没有基础类初始化，则输出结果是：</p>

  <p>not finalizing bases</p>

  <p>Creating Characteristic is alive</p>

  <p>LivingCreature()</p>

  <p>Creating Characteristic has heart</p>

  <p>Animal()</p>

  <p>Creating Characteristic can live in water</p>

  <p>Amphibian()</p>

  <p>Frog()</p>

  <p>bye!</p>

  <p>Frog finalize</p>

  <p>finalizing Characteristic is alive</p>

  <p>finalizing Characteristic has heart</p>

  <p>finalizing Characteristic can live in water</p>

  <p>从中可以看出确实没有为基础类Frog调用收尾模块。但假如在命令行加入“finalize”自变量，则会获得下述结果：</p>

  <p>Creating Characteristic is alive</p>

  <p>LivingCreature()</p>

  <p>Creating Characteristic has heart</p>

  <p>Animal()</p>

  <p>Creating Characteristic can live in water</p>

  <p>Amphibian()</p>

  <p>Frog()</p>

  <p>bye!</p>

  <p>Frog finalize</p>

  <p>Amphibian finalize</p>

  <p>Animal finalize</p>

  <p>LivingCreature finalize</p>

  <p>finalizing Characteristic is alive</p>

  <p>finalizing Characteristic has heart</p>

  <p>finalizing Characteristic can live in water</p>

  <p>尽管成员对象按照与它们创建时相同的顺序进行收尾，但从技术角度说，并没有指定对象收尾的顺序。但对于基础类，我们可对收尾的顺序进行控制。采用的最佳顺序正是在这里采用的顺序，它与初始化顺序正好相反。按照与C++中用于“破坏器”相同的形式，我们应该首先执行衍生类的收尾，再是基础类的收尾。这是由于衍生类的收尾可能调用基础类中相同的方法，要求基础类组件仍然处于活动状态。因此，必须提前将它们清除（破坏）。</p>

  <p>7.7.3 构建器内部的多形性方法的行为</p>

  <p>构建器调用的分级结构（顺序）为我们带来了一个有趣的问题，或者说让我们进入了一种进退两难的局面。若当前位于一个构建器的内部，同时调用准备构建的那个对象的一个动态绑定方法，那么会出现什么情况呢？在原始的方法内部，我们完全可以想象会发生什么――动态绑定的调用会在运行期间进行解析，因为对象不知道它到底从属于方法所在的那个类，还是从属于从它衍生出来的某些类。为保持一致性，大家也许会认为这应该在构建器内部发生。</p>

  <p>但实际情况并非完全如此。若调用构建器内部一个动态绑定的方法，会使用那个方法被覆盖的定义。然而，产生的效果可能并不如我们所愿，而且可能造成一些难于发现的程序错误。</p>

  <p>从概念上讲，构建器的职责是让对象实际进入存在状态。在任何构建器内部，整个对象可能只是得到部分组织――我们只知道基础类对象已得到初始化，但却不知道哪些类已经继承。然而，一个动态绑定的方法调用却会在分级结构里“向前”或者“向外”前进。它调用位于衍生类里的一个方法。如果在构建器内部做这件事情，那么对于调用的方法，它要操纵的成员可能尚未得到正确的初始化――这显然不是我们所希望的。</p>

  <p>通过观察下面这个例子，这个问题便会昭然若揭：</p>

  <p>//: PolyConstructors.java</p>

  <p>// Constructors and polymorphism</p>

  <p>// don't produce what you might expect.</p>

  <p>abstract class Glyph {</p>

  <p>abstract void draw();</p>

  <p>Glyph() {</p>

  <p>System.out.println("Glyph() before draw()");</p>

  <p>draw();</p>

  <p>System.out.println("Glyph() after draw()");</p>

  <p>}</p>

  <p>}</p>

  <p>class RoundGlyph extends Glyph {</p>

  <p>int radius = 1;</p>

  <p>RoundGlyph(int r) {</p>

  <p>radius = r;</p>

  <p>System.out.println(</p>

  <p>"RoundGlyph.RoundGlyph(), radius = "</p>

  <p>+ radius);</p>

  <p>}</p>

  <p>void draw() {</p>

  <p>System.out.println(</p>

  <p>"RoundGlyph.draw(), radius = " + radius);</p>

  <p>}</p>

  <p>}</p>

  <p>public class PolyConstructors {</p>

  <p>public static void main(String[] args) {</p>

  <p>new RoundGlyph(5);</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在Glyph中，draw()方法是“抽象的”（abstract），所以它可以被其他方法覆盖。事实上，我们在RoundGlyph中不得不对其进行覆盖。但Glyph构建器会调用这个方法，而且调用会在RoundGlyph.draw()中止，这看起来似乎是有意的。但请看看输出结果：</p>

  <p>Glyph() before draw()</p>

  <p>RoundGlyph.draw(), radius = 0</p>

  <p>Glyph() after draw()</p>

  <p>RoundGlyph.RoundGlyph(), radius = 5</p>

  <p>当Glyph的构建器调用draw()时，radius的值甚至不是默认的初始值1，而是0。这可能是由于一个点号或者屏幕上根本什么都没有画而造成的。这样就不得不开始查找程序中的错误，试着找出程序不能工作的原因。</p>

  <p>前一节讲述的初始化顺序并不十分完整，而那是解决问题的关键所在。初始化的实际过程是这样的：</p>

  <p>(1) 在采取其他任何操作之前，为对象分配的存储空间初始化成二进制零。</p>

  <p>(2) 就象前面叙述的那样，调用基础类构建器。此时，被覆盖的draw()方法会得到调用（的确是在RoundGlyph构建器调用之前），此时会发现radius的值为0，这是由于步骤(1)造成的。</p>

  <p>(3) 按照原先声明的顺序调用成员初始化代码。</p>

  <p>(4) 调用衍生类构建器的主体。</p>

  <p>采取这些操作要求有一个前提，那就是所有东西都至少要初始化成零（或者某些特殊数据类型与“零”等价的值），而不是仅仅留作垃圾。其中包括通过“合成”技术嵌入一个类内部的对象句柄。如果假若忘记初始化那个句柄，就会在运行期间出现违例事件。其他所有东西都会变成零，这在观看结果时通常是一个严重的警告信号。</p>

  <p>在另一方面，应对这个程序的结果提高警惕。从逻辑的角度说，我们似乎已进行了无懈可击的设计，所以它的错误行为令人非常不可思议。而且没有从编译器那里收到任何报错信息（C++在这种情况下会表现出更合理的行为）。象这样的错误会很轻易地被人忽略，而且要花很长的时间才能找出。</p>

  <p>因此，设计构建器时一个特别有效的规则是：用尽可能简单的方法使对象进入就绪状态；如果可能，避免调用任何方法。在构建器内唯一能够安全调用的是在基础类中具有final属性的那些方法（也适用于private方法，它们自动具有final属性）。这些方法不能被覆盖，所以不会出现上述潜在的问题。</p>

  <p>7.8 通过继承进行设计</p>

  <p>学习了多形性的知识后，由于多形性是如此“聪明”的一种工具，所以看起来似乎所有东西都应该继承。但假如过度使用继承技术，也会使自己的设计变得不必要地复杂起来。事实上，当我们以一个现成类为基础建立一个新类时，如首先选择继承，会使情况变得异常复杂。</p>

  <p>一个更好的思路是首先选择“合成”――如果不能十分确定自己应使用哪一个。合成不会强迫我们的程序设计进入继承的分级结构中。同时，合成显得更加灵活，因为可以动态选择一种类型（以及行为），而继承要求在编译期间准确地知道一种类型。下面这个例子对此进行了阐释：</p>

  <p>//: Transmogrify.java</p>

  <p>// Dynamically changing the behavior of</p>

  <p>// an object via composition.</p>

  <p>interface Actor {</p>

  <p>void act();</p>

  <p>}</p>

  <p>class HappyActor implements Actor {</p>

  <p>public void act() {</p>

  <p>System.out.println("HappyActor");</p>

  <p>}</p>

  <p>}</p>

  <p>class SadActor implements Actor {</p>

  <p>public void act() {</p>

  <p>System.out.println("SadActor");</p>

  <p>}</p>

  <p>}</p>

  <p>class Stage {</p>

  <p>Actor a = new HappyActor();</p>

  <p>void change() { a = new SadActor(); }</p>

  <p>void go() { a.act(); }</p>

  <p>}</p>

  <p>public class Transmogrify {</p>

  <p>public static void main(String[] args) {</p>

  <p>Stage s = new Stage();</p>

  <p>s.go(); // Prints "HappyActor"</p>

  <p>s.change();</p>

  <p>s.go(); // Prints "SadActor"</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>在这里，一个Stage对象包含了指向一个Actor的句柄，后者被初始化成一个HappyActor对象。这意味着go()会产生特定的行为。但由于句柄在运行期间可以重新与一个不同的对象绑定或结合起来，所以SadActor对象的句柄可在a中得到替换，然后由go()产生的行为发生改变。这样一来，我们在运行期间就获得了很大的灵活性。与此相反，我们不能在运行期间换用不同的形式来进行继承；它要求在编译期间完全决定下来。</p>

  <p>一条常规的设计准则是：用继承表达行为间的差异，并用成员变量表达状态的变化。在上述例子中，两者都得到了应用：继承了两个不同的类，用于表达act()方法的差异；而Stage通过合成技术允许它自己的状态发生变化。在这种情况下，那种状态的改变同时也产生了行为的变化。</p>

  <p>7.8.1 纯继承与扩展</p>

  <p>学习继承时，为了创建继承分级结构，看来最明显的方法是采取一种“纯粹”的手段。也就是说，只有在基础类或“接口”中已建立的方法才可在衍生类中被覆盖，如下面这张图所示：</p>

  <p>可将其描述成一种纯粹的“属于”关系，因为一个类的接口已规定了它到底“是什么”或者“属于什么”。通过继承，可保证所有衍生类都只拥有基础类的接口。如果按上述示意图操作，衍生出来的类除了基础类的接口之外，也不会再拥有其他什么。</p>

  <p>可将其想象成一种“纯替换”，因为衍生类对象可为基础类完美地替换掉。使用它们的时候，我们根本没必要知道与子类有关的任何额外信息。如下所示：</p>

  <p>也就是说，基础类可接收我们发给衍生类的任何消息，因为两者拥有完全一致的接口。我们要做的全部事情就是从衍生上溯造型，而且永远不需要回过头来检查对象的准确类型是什么。所有细节都已通过多形性获得了完美的控制。</p>

  <p>若按这种思路考虑问题，那么一个纯粹的“属于”关系似乎是唯一明智的设计方法，其他任何设计方法都会导致混乱不清的思路，而且在定义上存在很大的困难。但这种想法又属于另一个极端。经过细致的研究，我们发现扩展接口对于一些特定问题来说是特别有效的方案。可将其称为“类似于”关系，因为扩展后的衍生类“类似于”基础类――它们有相同的基础接口――但它增加了一些特性，要求用额外的方法加以实现。如下所示：</p>

  <p>尽管这是一种有用和明智的做法（由具体的环境决定），但它也有一个缺点：衍生类中对接口扩展的那一部分不可在基础类中使用。所以一旦上溯造型，就不可再调用新方法：</p>

  <p>若在此时不进行上溯造型，则不会出现此类问题。但在许多情况下，都需要重新核实对象的准确类型，使自己能访问那个类型的扩展方法。在后面的小节里，我们具体讲述了这是如何实现的。</p>

  <p>7.8.2 下溯造型与运行期类型标识</p>

  <p>由于我们在上溯造型（在继承结构中向上移动）期间丢失了具体的类型信息，所以为了获取具体的类型信息――亦即在分级结构中向下移动――我们必须使用 “下溯造型”技术。然而，我们知道一个上溯造型肯定是安全的；基础类不可能再拥有一个比衍生类更大的接口。因此，我们通过基础类接口发送的每一条消息都肯定能够接收到。但在进行下溯造型的时候，我们（举个例子来说）并不真的知道一个几何形状实际是一个圆，它完全可能是一个三角形、方形或者其他形状。</p>

  <p>为解决这个问题，必须有一种办法能够保证下溯造型正确进行。只有这样，我们才不会冒然造型成一种错误的类型，然后发出一条对象不可能收到的消息。这样做是非常不安全的。</p>

  <p>在某些语言中（如C++），为了进行保证“类型安全”的下溯造型，必须采取特殊的操作。但在Java中，所有造型都会自动得到检查和核实！所以即使我们只是进行一次普通的括弧造型，进入运行期以后，仍然会毫无留情地对这个造型进行检查，保证它的确是我们希望的那种类型。如果不是，就会得到一个ClassCastException（类造型违例）。在运行期间对类型进行检查的行为叫作“运行期类型标识”（RTTI）。下面这个例子向大家演示了RTTI的行为：</p>

  <p>//: RTTI.java</p>

  <p>// Downcasting &amp; Run-Time Type</p>

  <p>// Identification (RTTI)</p>

  <p>import java.util.*;</p>

  <p>class Useful {</p>

  <p>public void f() {}</p>

  <p>public void g() {}</p>

  <p>}</p>

  <p>class MoreUseful extends Useful {</p>

  <p>public void f() {}</p>

  <p>public void g() {}</p>

  <p>public void u() {}</p>

  <p>public void v() {}</p>

  <p>public void w() {}</p>

  <p>}</p>

  <p>public class RTTI {</p>

  <p>public static void main(String[] args) {</p>

  <p>Useful[] x = {</p>

  <p>new Useful(),</p>

  <p>new MoreUseful()</p>

  <p>};</p>

  <p>x[0].f();</p>

  <p>x[1].g();</p>

  <p>// Compile-time: method not found in Useful:</p>

  <p>//! x[1].u();</p>

  <p>((MoreUseful)x[1]).u(); // Downcast/RTTI</p>

  <p>((MoreUseful)x[0]).u(); // Exception thrown</p>

  <p>}</p>

  <p>} ///:~</p>

  <p>和在示意图中一样，MoreUseful（更有用的）对Useful（有用的）的接口进行了扩展。但由于它是继承来的，所以也能上溯造型到一个Useful。我们可看到这会在对数组x（位于main()中）进行初始化的时候发生。由于数组中的两个对象都属于Useful类，所以可将f()和g()方法同时发给它们两个。而且假如试图调用u()（它只存在于MoreUseful），就会收到一条编译期出错提示。</p>

  <p>若想访问一个MoreUseful对象的扩展接口，可试着进行下溯造型。如果它是正确的类型，这一行动就会成功。否则，就会得到一个ClassCastException。我们不必为这个违例编写任何特殊的代码，因为它指出的是一个可能在程序中任何地方发生的一个编程错误。</p>

  <p>RTTI的意义远不仅仅反映在造型处理上。例如，在试图下溯造型之前，可通过一种方法了解自己处理的是什么类型。整个第11章都在讲述Java运行期类型标识的方方面面。</p>

  <p>7.9 总结</p>

  <p>“多形性”意味着“不同的形式”。在面向对象的程序设计中，我们有相同的外观（基础类的通用接口）以及使用那个外观的不同形式：动态绑定或组织的、不同版本的方法。</p>

  <p>通过这一章的学习，大家已知道假如不利用数据抽象以及继承技术，就不可能理解、甚至去创建多形性的一个例子。多形性是一种不可独立应用的特性（就象一个switch语句），只可与其他元素协同使用。我们应将其作为类总体关系的一部分来看待。人们经常混淆Java其他的、非面向对象的特性，比如方法过载等，这些特性有时也具有面向对象的某些特征。但不要被愚弄：如果以后没有绑定，就不成其为多形性。</p>

  <p>为使用多形性乃至面向对象的技术，特别是在自己的程序中，必须将自己的编程视野扩展到不仅包括单独一个类的成员和消息，也要包括类与类之间的一致性以及它们的关系。尽管这要求学习时付出更多的精力，但却是非常值得的，因为只有这样才可真正有效地加快自己的编程速度、更好地组织代码、更容易做出包容面广的程序以及更易对自己的代码进行维护与扩展。</p>

  <p>7</p>

  <div class="mbppagebreak"></div>
</body>
</html>
